
\section{孤立日程系统的第一阶段功能}
\label{sec:function-1stlevel}

这节我们主要讨论\underline{孤立日程系统}，
指的是日程系统副本的用户只有一人，
该日程系统不需要和其他用户以及其他日程系统副本进行交互。

在孤立日程系统的基础上，本节主要讨论第一阶段功能，
即：记录用户的安排，及时地进行筛选和提醒。
功能上相当于一个附加有“免抄写、带提醒”功能的纸质日程系统。

为方便起见，以下统称日程系统中的各种提醒、预约、任务等为\underline{事件}。

\subsection{事件的时间属性}

事件的时间属性可以说是系统最基本的属性。
如果系统中的各种事件都没有时间相关的属性，那这个系统就是一个TodoList……

一般来说，虽然事件的时间属性种类有很多，但大部分属性一般都是空的，
只有少数事件属性被设置，并起作用，以下不再一一说明。
另外，由于本节不讨论重复模型，所以重复相关的时间属性这里不作介绍，
同样地，关于多层任务的时间属性这里也不作介绍。

\subsubsection{提醒}
对于提醒，常见的时间属性有
\begin{description}
\item[提醒时刻] 当到达指定时刻时，系统应该主动提醒用户。

\item[起始可见时刻] 当系统时间超过指定时刻是，该提醒就显示在系统的下一步
事件列表上，但这时一般还没到提醒时刻，是一种被动地向用户预提醒的方式。

\item[Deadline] 表示该提醒应该在指定时间之前完成。

\item[失效时刻] 超过了指定时间用户仍未把某提醒标记为完成，
那该提醒自动失效，标记为未完成，不再提醒用户，一般用于可选事件。
\end{description}

\subsubsection{预约}
对于预约，常见的时间属性有
\begin{description}
\item[起始时刻] 表示预约开始的时间。

\item[预约长度] 表示该预约持续的时间。

\item[准备时刻、出发时刻（或准备时间）] 由于预约一般需要场所的切换，
需要一定的时间来准备并前往预定地点，这个时刻一般早于起始时刻。
如果是准备时间，则是指相对起始时刻需要提前的时间长度。

\item[恢复时刻、返回时刻（或恢复时间）] 指在预约结束后返回至普通状态的时间，
不过习惯上一般添加在预约长度中。
从个人观点来讲，只有在引入概率模型后预约长度和恢复时间才能真正地发挥作用，
因为一般来讲这种协作事件的耗时是随机的，恢复时间也是随机的。

\end{description}

\subsubsection{任务}
对于任务，常见的时间属性有
\begin{description}
\item[起始可见时刻] 同前。

\item[生效时刻] 表示在生效时刻之后，任务进入随时可以被处理的状态，
一般是等待其他事件或资源。

\item[Deadline] 表示任务应该在指定时刻之前完成。
\item[失效时刻] 同前。
\end{description}



\subsection{时区、农历及时间抽象层}

\subsubsection{时区}

应该说，时区这个问题在孤立日程系统中是不存在的，或者说是极少存在的，
因为绝大部分人都长时间生活在某个固定时区中，任务的时间都是和当地时间密切相关的，
例如吃饭的时间，上班的时间等，所以改变时区的时候只要改下系统时间基本就可以了。

这里我们就可以看出两种需要时区概念的需求
\begin{enumerate}
\item 
非孤立日程系统，即该日程系统需要和其他用户的日程系统进行交互。

\item
某人频繁在各时区中穿梭，而且任务的时间是绝对时间的，不和当地时间绑定，
如国际视频会议、全球同时发布等。

\item
夏令时问题也可以看作一种动态时区问题，但和前面讨论的一样，
它可以作为一种时区的改变来处理，简单地改变系统时间就可以满足需求，
所以这里不把它作为一种技术需求来看\footnote{但这确实是用户需求之一}。
但在具体实现时区时，需要考虑这种动态时区问题。
\end{enumerate}

第一个需求是比较常见的，虽然这节讨论的是孤立日程系统的需求，由于它和其他功能相关，所以还是讨论下。
第二个需求则较为罕见，而且我并不了解这种用户的状态，所以只是在下面的一小节进行理想分析。

和其他用户进行交互时，就需要考虑国际化的问题\footnote{受到了徐杰同学的提示。}，
即我们是在全球范围内考虑用户的交互，这时就有一个绝对时间概念，一般取为UTC时间，
各日程系统将其投影到自己的时区内，也可以根据用户的要求同时显示其他时区的
投影时间\footnote{方便用户考虑其他时区内人员的工作时间。}。

对于基于当前时区的任务，也可以附加上时区信息，以实现在不同日程系统间进行交互，
这就需要用户在设置时间属性时，指定该时间是绝对时间还是和当前时区绑定的相对时间，当然也可以绑定到某个指定时区。

需要说明的是，在联系人模块中，就需要记录各联系人的默认时区，方便用户设置任务的时区信息和时间。

\subsubsection{农历}

农历是中国的常用时间基准之一，不少任务要依赖于农历，如农历节日和农历生日。
其他文化中也会有类似的非公历历法需要处理，这也是国际化带来的问题之一。

需要注意的是，历法问题和时区问题其实是正交的，时区是对基准时间的线性偏移，
而历法是对日期的重新表示，两者可以任意组合。但由于农历常常和其文化主体所在地相关，
所以农历似乎就有了默认时区（即北京的UTC+8），例如在美国的留学生观看中国的春晚直播。
但可能会有其他情况使得用户使用的历法可能与用户当前时区相组合，如在美国的留学生过农历生日。

公历本身并非“完美”，它也有不连续的地方\footnote{
详见\href{http://en.wikipedia.org/wiki/Gregorian_calendar}{\emph{Wiki}}。}。

\subsubsection{伪时区问题}

伪时区问题是指：对于夜猫子来说，对于某些任务来讲，直到他们睡觉为止，
当前的日期保持不变，特别是不和其他人进行交互的任务。
例如对于很多宅人来讲，日本动画的播放时间可以是25:00、26:30这样的时间，
这种时间对他们来讲，并不是第二天。
而且他们睡醒之后的任务也不是今天（即前一天）的任务，如第二晚上的任务，
但对于传统的日程系统来说，这已经是一天了，因为用户已经过了24:00。

这就好像用户生活在别的时区一样，但用户有大量的任务要和他所在的时区相关，
所以单纯地“伪造”时区是不能解决问题的。
对于夜班工作的人，或是工作一整天、休息一整天这样的人来说，应该也有类似的问题。
由于这个问题看起来和时区问题很像，又并非单纯的时区问题，所以我称这个问题是伪时区问题。

伪时区问题其实是天的划分问题，即对于夜猫子来讲，他们的一天从他们睡醒（一般是中午）开始，
到他们睡觉（一般是后半夜）结束，不管这中间是否经过了0:00，
当然也可能有人有更混乱的作息，例如不以24小时为周期的，这在某些工作量突发性较强的人群中也是很常见的。

总的来说，这并不是日程系统内核关注的问题，内核只需按绝对时间处理即可。
伪时区问题直接决定了要把哪些任务显示给用户，应该属于内核与用户UI之间的
业务逻辑层\footnote{这里说业务逻辑层并不准确，因为内核也包含基础的业务逻辑。也可以把业务逻辑层直接放入内核中。}。


\subsubsection{频繁切换时区的人}

对于频繁切换时区的人，基本上想要跟踪他生物钟的调整是比较困难的，
所以像吃饭、睡觉、工作时间就很难在日程系统中进行估计。
虽然可以通过一些方式让用户来进行设置，但我感觉用户的生物钟应该是不稳定的，能估计的精度很低。

对于他们来说，需要在界面上快速调整时区，或者在某些时候直接工作在UTC时区上或不显示绝对时间（例如在飞机上），
对于他们来说时区和资源一样是快速变化的，这点在UI设计时必须要考虑。
对于一般用户来讲，这几乎是永远也用不上的功能。

\subsubsection{时间抽象层}

为了解决这些实现相关的需求问题，可以引入一些抽象层，如
\begin{enumerate}
\item
绝对时间线，一般默认取UTC时间。

\item
用户实际时间线，可以是多条时间线，包含时区、农历、伪时区等概念的处理，一般是很少变动的。
用户实际时间线也可以有内部的层级依赖关系，如绑定了特定时区和农历的时间线。

\item
任务时间线，提供任务和学校常见的周次概念或其他比天更大的时间概念，
这些时间概念可以频繁添加，或者和任务绑定。
任务时间线也可以作为用户实际时间线，这是从用户需求上的划分，而非技术上。

\end{enumerate}

用户在设置时间时，可以选择上面三种层次中的任意一个，
内核的调度算法应该主要工作在绝对时间，或是用户当前公历时间和当前时区上，
更高层的功能进行各时间层的转换。

任务时间线也可以由用户添加各种复杂的时间模式，如12小时、48小时或72小时的循环周期等，
满足用户对时间基准的各种变态需求，这方面在功能上和固定重复任务是有关的，
但从用户需求的角度最好还是分开处理。


\subsection{场所和资源}

\subsubsection{场所}
场所这个概念主要引入了环境对事件的限制，指出某些事件需要在特定环境才能进行。
在办公室能做的事情和在家里能做的事情肯定是不同的。

\paragraph{最简单的场所模型}
这个功能的模型可以是非常简单的，那就是在系统中添加一组场所，
每个事件在添加时设置其可以在那些场所完成%
\footnote{实际中一般事件都是使用默认设置，即可以在任何场所完成。}。
在日程系统使用时，用户需要指定当前所处的场所，这样系统就可以根据当前场所
和任务的场所设置来显示可以在当前场所完成的任务，
也可以提醒用户有哪些即将到期的需要在其他场所完成的任务。

这种模型在实际程序中是可以见到的，但一般用户只有在系统内事件较多的时候才使用，
目的主要是过滤不合适的事件。该模型其实也可以提醒用户在适合的时候进行场所切换，
但尚未见到这种实现。而且这个模型过于简单，场所切换的提醒的“质量”值得怀疑。

\paragraph{资源可用性模型}
需要注意的是，场所本身代表着一组资源，所以当场所本身也复杂多变时，
可以引入资源的概念，例如网络、电话、打印机等，
并设置每一个场所拥有的资源，把事件对场所的依赖改为事件对资源的依赖，
系统选择事件时也是根据当前场所能否满足对资源的需求来选择。

不过这种模型对于一般用户来说似乎用处很小，毕竟常见的场所是很少的，
而且一般都很固定。但引入这个模型也是有意义的，它可以方便地设置临时场所，
只要勾选一下该场所拥有的资源即可。而且对于暂时的环境突变也有较强的适应能力，
如突然某天办公室的打印机坏了，那只需要以原来的办公室场所为模板，
创建一个临时场所，并去掉其中的打印机资源即可。

\subsubsection{复杂资源模型}
\label{sec:function-1stlevel.resource}

前面已经介绍了资源可用性模型，引入了资源的概念，
但那个资源只是一种2状态的模型，即只有可用、不可用两种状态。
实际生活中不少事情中抽象出的资源是有数量概念的，
比如剩余的茶叶包数量，这些资源不光带有数量的信息，
而且\emph{其数量经常会伴随某些事件的发生发生增减}。
例如每天早上泡茶会定期消耗固定数量的茶叶，
当茶叶接近耗尽时，我们需要采购茶叶。
如果想把这些琐事添加到日程系统中，不用当茶叶耗尽时才想起要去买茶叶，
那就势必引入有数量的资源这一概念，以及在事件的属性中添加资源增减相关的信息，
可以进一步添加当某些资源数量符合某一条件时就触发的触发型事件。
进而可以实现：在茶叶数量小于阈值后可以随着数量的减少不断地自动提高采购茶叶任务的优先级。

这个模型实现起来较为复杂，但它增加的功能也是相当多的，
可变数量资源并非一定要对应实际的资源，也可以是距离下次打扫卫生的天数，
很多\footnote{从我现在来看似乎是“所有”。}不固定的重复事件都可以
添加到这种模型中。

先不说这个模型对用户的要求有多高\footnote{只要UI设计合理，
理工科的同学应该是不成问题的。}，其实现就给日程系统的很多高级功能
带来了相当大的困难，不说增加各种资源状态的维度，这个模型实质上
是把涉及相同资源的事件耦合在一起，而且这些事件往往还是某种重复事件，
在综合规划时需要的计算量明显增大。不过好在实际使用中一般某种资源都指涉及
一个消耗事件和一个补充事件，耦合度并不是很高。
如果用户真的有那种复杂的耦合事件，那在没有我们的日程系统之前，
用户“必然”是没法自己解决这种问题的，所以我们一般也不会比用户做的更差。

\subsubsection{资源信息的自动获取}

日程系统在某种程度上来说就是一个获取信息和显示处理过的结果的程序\footnote{所有的程序都这样}，
输入的信息一方面是用户输入的各种事件信息，另一方面是时间。
所以正确的系统时间对日程系统是必要的。

引入了资源之后，资源的状态信息的更新就成了一个问题。有很多信息的状态是可以信息化的，
例如说网络是否可用，在极影上追的新番是否已经发布等等。
问题在于，不计其数的各种资源状态的获取需要插件系统或嵌入脚本的支持。


\subsection{优先级}

个人感觉优先级这种东西在孤立日程系统上有些鸡肋，虽然它看起来很好，
用起来总不是那么回事。Toodledo的动态优先级算是一个相当不错的尝试。
不过总的来说这应该算是自我管理的一环吧，没有特别强的自我管理意识的话一般还是挑容易做的任务先做的，
除非碰到特别紧急的，动态优先级之所以会被用户使用大概就是这种原因吧。

优先级应该是在系统安排任务时使用的，当多个同类任务需要安排或发生冲突时，
优先处理优先级高的任务是很不错的办法，毕竟还是程序适合这种工作。

不过我感觉单一的线性优先级似乎过于死板，而且不方便调节。
可以考虑引入类别的概念，如工作中的高中低、生活中的高中低、家庭的高中低等等，
每类事情有自己的优先级，而如何把多个类别的优先级映射到实际的单一优先级是可以定制的。
例如在工作压力大的时候可以增加工作类优先级的总体优先级，使得所有工作类的任务的实际优先级同步提升。
我没有试过，所以并不清楚用户对此的看法。

\subsection{事件依赖}

所谓的依赖，指的是事件要按照指定的先后顺序完成，不过这一般都是在预约和任务之间的，
好像没“提醒”什么事。

最常见的依赖应该是那种多步骤顺序完成的任务，
设定起来也较为自然，不过缺乏灵活性，适合不是那么复杂的任务。

完整的依赖关系肯定就是搞一个无圈有向图出来，判断无圈是有现成算法的，
不过各个任务和预约之间还有时间上的关系，这个貌似就不好判断了，
而且没有直接依赖关系的预约和任务也会影响这些任务能否完成。
还是最后枚举出一个实际日程比较容易，但计算量可不小。



\subsection{目录与复合任务}
\label{sec:function-1stlevel.subtask}

\subsubsection{目录模型}
所谓的目录以及任务的分层（在大任务中包含子任务）\footnote{目录是一种最简化的复合任务模型。}，
都是一种按内容聚合任务的方式，以防某个大任务的众多子任务淹没了其他。

就实现来说，最简单的实现就是文件夹的翻版，多层任务也是如此，
但包含子任务的复杂任务的时间属性又该如何设置呢？或者说那些属性的意义是什么呢？
如果复杂任务的Deadline设置的比子任务的Deadline还要早，那又该如何处理呢？
这些都是很头疼的，不少问题似乎违背直觉，用户不会想看到这样的东西。

但如果取消上层任务的时间属性，那似乎会带来使用上的不便。
一般使用中任务都是逐级规划的，进行到某一步骤时，才对这一步骤进行规划，
创建下级子任务，而后续的步骤则仍然保持简单任务的状态，他们之间的关系应该如何处理呢？
在创建子任务的时候丢弃原任务的时间属性似乎是一种办法，但好像不那么优雅。
另外是否会来带使用上的不便尚不清楚。
但可以肯定的是，目录式的多层任务是容易实现的，用户体验只有用过了才知道。

\subsubsection{“真”多层任务模型}

这里介绍另一种多层任务模型，它追求的是尽可能地保留原任务信息，使得在添加子任务时对原有信息影响最小。
那么很显然，高层的任务将仍然带有原来的时间属性。这里把高层任务的时间属性看作是对下层子任务的时间属性的限制条件，
当发生冲突时，以高层任务为准，更重要的是，当发生非正常的情况时，应该对用户提出警告，
例如上面说的，高层任务的Deadline比子任务的Deadline还要早。

这种模型使得在创建子任务的工作量减轻了，因为子任务自然地受到高层任务的Deadline之类的时间属性制约，
一般只需要指定子任务的依赖关系（通常都是顺序完成、或者完全没有依赖关系）即可。
或者可以提前子任务的Deadline（其他时间属性类似），当子任务的Deadline晚于高层任务时，
可以视作一种违反常理的情况，应该对用户提出警告，如果用户无视，则可以考虑仍以高层任务为准。

这种实现对用户的友好性增强了一些，但仍需要对用户进行必要的解释。此外针对不常见的各类情况需要
设计人员仔细思考，指定出各种各样的限制条件和优先顺序等，而且不同类型的时间属性之间也可能会发生冲突。
总之对于实现人员来讲，大概是一个颇为复杂的实现方案，而且也不见得优雅。


\subsection{待规划任务}

\ref{sec:function-1stlevel.subtask} 中已经提到，
复杂任务往往是一般进行一边规划的，此外往往有些任务来不及规划就被放入了日程系统，
等待进一步的规划和任务量评估（参考GTD的收集篮）。
这类任务有一个特点，那就是将要花费的时间是完全不确定的，在用户专门设置之前，
日程系统不能对其长度做任何假设，更为糟糕的是，在仔细规划前往往连Deadline是什么时候也不知道。

请注意，不知道Deadline不意味着没有Deadline，如果一个任务处在待规划状态太长时间，
以至于尚未规划便过期了，这种情况可是相当糟糕的。
所以日程系统必须要对待规划任务做特殊处理，尽可能地提醒用户尽早做初期规划。

具体实现方式大概也就是记录一下还有多少待规划任务，在日程系统上弄个“紧急，还有××任务未作规划……”
之类的东西。总之这个需求是存在的，但目前尚未有较好的解决方案（GTD也不例外）。

\subsection{触发式的ToDoList}

前面讨论的基于时间和资源触发的事件仍然不能覆盖生活中的所有需求，例如，待采购物品清单。
如果什么时候去超市完全是由用户心情决定，那么系统就没法安排采购行为。
但待采购的物品却是在平时不断积累的，既然日程系统很常用，那么把它记在日程系统中应该是最为方便的，
但问题是它究竟是提醒、预约、任务中的哪个呢？答案是哪个也不是，因为它没有触发时间\footnote{它根本就不是靠时间触发的。}，
它需要用户主动触发，告诉系统我马上要去超市了，显示下需要采购的物品列表，这不能归入前面讨论的三类基本模型。
而且，它可以从日程系统的其他事件中获得输入\footnote{某个事件预言某样东西要消耗完了。这在前面的有数量资源模型中很常见。}，
也可以在ToDoList中的某些事件中设置Deadline等其他时间属性，这样日程系统就可以及时提醒用户进行采购。

\subsection{历史查询}

已完成和已过期的任务也应该保存在系统中，一来为日后回顾提供了可能，
二来是当系统模型很复杂参数较多时，过去日程的信息可以辅助用户设置这些参数，
程序也可以利用这些信息做一些估计。

历史信息的保存首先遇到的问题是，分类的改变，原来有某种分类，或是某种资源，
但后来这个资源被删除了，这些变化应该如何处理就成了一个问题。
一个可行的方法是，去除所谓的删除功能，将其改为标记为废弃（当然不是完全剔除，只是不再作为常用功能了）。

如果想从过去的信息中总结出有用的东西，要求用户在使用时不能把“垃圾”丢进去，
没完成就是没完成，延时了就是延时了，违背实际的信息应该尽量少输入。


\subsection{自动任务}

有很多任务是可以自动完成的，如清理临时文件、查看××更新等，
这些任务可以通过调用外部程序或插件来完成。但有一个问题是，根据这些任务的执行结果，
可能需要进一步在日程系统中添加其他事件，这应该由外部程序或插件返回给日程系统中。

这就涉及到给日程系统中常见的提醒、任务、预约等模型对外提供API，或者描述形式，
以便和其他程序进行交互。这里的API一般是获取整个日程系统的状态，修改日程系统中的事件等，
添加事件可以仅仅使用一个公开的事件描述格式来进行通信。

\subsection{模板}

随着功能的不断增加，每个事件可以或需要指定的属性也越来越多，
添加一个事件需要制定20个属性也不是天方夜谭，但每次都输入所有参数无论是对于
GUI用户还是命令行用户都是不能接受的\footnote{但对于API来说这是比较简单的方式。}。

好在很多属性可以有默认值，或者根据不同的分类分别指定默认值，实现快速事件添加。
当然如果采用GTD的方式，用户在最初添加事件的时候可以只输入标题和一部分内容，
其他属性留待以后整理时再添加。

其实，默认值就是模板的一种简化模型，模板是指包含部分或全部属性设置的属性集，
这个属性可以是针对事件的，也可以之针对其他对象的，只要出现输入数据的维度较大，
但常用输入集较少的时候，模板就有用武之地。

在用户使用的时候，可以通过设定一些常见情况的模板，来实现快速添加或设置属性或事件。
例如可以设置一个“琐事”模板，它包含属性“默认不显示”、“优先级低”、“任务已完成规划”、
“不需要任何资源”……模板也可以是针对事件的，如进行多步骤任务时，往往需要添加多个事件，
像洗衣服就需要“整理要洗的衣服投入洗衣机”\footnote{这项一般是不添加的，因为在添加的时候或者添加之前就直接干了。}、
“40分钟后提醒我取出洗完的衣服并晾晒”、“第二天提醒我收起晾晒的衣服”。
这样用户每次开始洗衣服时就从“洗衣服模板”添加一个事件组就可以了。

这里称按照模板产生一组事件为应用该模板。模板也可以带有参数，这些参数在应用模板时输入，
使得模板可以根据参数设置子事件的属性，如在上面洗衣服模板的例子中，用户应用该模板的时间就是需要传给模板的参数，
这样就可以计算出“40分钟后”、“第二天”的具体时间。


\subsection{重复事件}
前面 \ref{sec:myexp.mr.repeat} 节已经介绍过重复事件的概念，并给出了一种实现方式，
这里不再重复，以下从整体设计角度进行介绍。

重复事件模型的关键在于要把重复事件单独作为一种对象进行处理，
而不是作为一种特殊事件，因为它们之间的差别很大，而且重复逻辑本身也较为复杂，
应该单独进行封装。

重复事件所产生的事件序列则可以作为一种事件和其他事件进行统一处理，
有的时候需要在子事件中记录产生它的父重复事件，以便实现某种批量修改等。

对于固定的重复事件，其主要工作在于提供描述重复事件序列的方式，
这方面对于农历、周次等常见描述方式都是要支持的。
对于不固定的重复事件，可以使用和重复事件相同的方式进行处理，
也可以采用 \ref{sec:function-1stlevel.resource} 节中给出的方式进行实现。

